package com.javatom.notes.redis.service;

import com.alibaba.fastjson.JSON;
import com.javatom.notes.redis.mapper.TestDataInfoMapper;
import com.javatom.notes.redis.po.ProcessPo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class RedisOperatService
{
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private TestDataInfoMapper testDataInfoMapper;

    /**
     * 操作string 类型
     * 常规的set/get操作，value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。
     * @return
     */
    public boolean saveStr() {
        List<ProcessPo> top200 = testDataInfoMapper.findTop200();
      //  NANOSECONDS  纳米秒
      //  MICRO SECONDS 微秒
        // MILLISECONDS Time unit representing one thousandth of a second 千分之一秒
        //SECONDS 秒
        //MINUTES 分
        //HOURS 小时
        //DAYS 天
        top200.forEach(e->{
            if(e.getAuditStatus().equals("1")){
                //设置过期时间
                redisTemplate.opsForValue().set(e.getId(),JSON.toJSONString(e),100, TimeUnit.MINUTES);
            }else if(e.getAuditStatus().equals("2")){
                redisTemplate.opsForValue().set(e.getId(),JSON.toJSONString(e),10000, TimeUnit.SECONDS);
            }else{
                redisTemplate.opsForValue().set(e.getId(),JSON.toJSONString(e),3, TimeUnit.HOURS);
            }
            //redisTemplate.opsForValue().set(e.getId(),JSON.toJSONString(e));
        });
        return true;
    }
    /**
     * 查找string类型
     * @return
     */
    public ProcessPo getStr(String keyId) {

        Object o = redisTemplate.opsForValue().get(keyId);

        if(null!=o){
            log.error("查找string类型 {}", o);
            return (ProcessPo)o;
        }

        return null;
    }

    /**
     * 查找string类型
     * @return
     */
    public void   updateStr( ProcessPo processPo) {

         redisTemplate.opsForValue().set(processPo.getId(),JSON.toJSONString(processPo));



    }

    /**
     * 操作List 类型
     * 使用List的数据结构，可以做简单的消息队列的功能。另外还有一个就是，可以利用lrange命令，做基于redis的分页功能，性能极佳，用户体验好。本人还用一个场景，
     * 很合适—取行情信息。就也是个生产者和消费者的场景。LIST可以很好的完成排队，先进先出的原则。
     * @return
     */
    public boolean operatList() {
        List<ProcessPo> top200 = testDataInfoMapper.findTop200();
        //  NANOSECONDS  纳米秒
        //  MICRO SECONDS 微秒
        // MILLISECONDS Time unit representing one thousandth of a second 千分之一秒
        //SECONDS 秒
        //MINUTES 分
        //HOURS 小时
        //DAYS 天
        top200.forEach(e->{
            //从左边插入，即插入到列表头部
            Long pojo_top200 = redisTemplate.opsForList().leftPush("pojo_top200", e);
            log.error("从左边插入，即插入到列表头部 {} ",pojo_top200);
            //redisTemplate.opsForValue().set(e.getId(),JSON.toJSONString(e));
        });
        //从左边插入一个数组
        String[] operat = new String[] {"redis_operat", "es_operat"};
        Long aLong = redisTemplate.opsForList().leftPushAll("arr:str", operat);
        log.error("从左边插入一个数组 {} ",aLong);
        //从左边插入一个集合
        Long vx = redisTemplate.opsForList().leftPushAll("pojo_list", top200);
        log.error("从左边插入一个集合 {} ",vx);
        //如果存在key对应的列表，则从左插入，不存在不做操作
        Long pojo_top200 = redisTemplate.opsForList().leftPushIfPresent("pojo_top200", top200.get(0));
        log.error("如果存在key对应的列表，则从左插入，不存在不做操作{} ",pojo_top200);

        //在key对应的列表中从左边开始找，找到第一个pivot，然后把value插到pivot左边，没有不做操作
      // 也可以从右边插入，把上面的left改为right即可
        Long pojo_top2001 = redisTemplate.opsForList().leftPush("pojo_top200", top200.get(0), top200.get(2));
        log.error("在key对应的列表中从左边开始找，找到第一个pivot，然后把value插到pivot左边，没有不做操作{} ",pojo_top2001);
        //指定位置重新设置指定值
        redisTemplate.opsForList().set("pojo_top200",1,top200.get(3));


        //删除和value相同的count个元素，count < 0，从右开始,count > 0，从左开始,count = 0，全部
        Long pojo_top2002 = redisTemplate.opsForList().remove("pojo_top200", -1, top200.get(0));
        log.error("删除和value相同的count个元素，count < 0，从右开始,count > 0，从左开始,count = 0，全部{} ",pojo_top2002);
        //获取制定下标对应的值 index,从0开始，有正负两套下标
        Object pojo_top2003 = redisTemplate.opsForList().index("pojo_top200", 1);
        log.error("从左边插入一个集合 {} ",pojo_top2003);
        //查询list中指定范围的内容
        List pojo_top2004 = redisTemplate.opsForList().range("pojo_top200", 0, -1);
        log.error("从左边插入一个集合 {} ",pojo_top2004);
        //修剪列表，使其只包含指定范围内的元素
        redisTemplate.opsForList().trim("pojo_top200", 0, 10);

        //查询列表长度
        Long pojo_top2005 = redisTemplate.opsForList().size("pojo_top200");
        log.error("查询列表长度 {} ",pojo_top2005);
        //弹出最左边元素
        Object pojo_top2006 = redisTemplate.opsForList().leftPop("pojo_top200");
        log.error("弹出最左边元素 {} ",pojo_top2006);
        //移出并获取列表的第一个元素， 如果列表没有元素会阻塞列表直到等待超时。
        Object pojo_top2007 = redisTemplate.opsForList().leftPop("pojo_top200", 10, TimeUnit.SECONDS);
        log.error("移出并获取列表的第一个元素， 如果列表没有元素会阻塞列表直到等待超时。 {} ",pojo_top2007);

        //弹出最右边元素
        Object pojo_top2008 = redisTemplate.opsForList().rightPop("pojo_top200");
        log.error("弹出最右边元素 {} ",pojo_top2008);


        //弹出k1最右边元素并放入k2最左边
        redisTemplate.opsForList().rightPopAndLeftPush("pojo_top200", "pojo_top20");
        return true;
    }

    /**
     * 操作map 类型
     * 这里value存放的是结构化的对象，比较方便的就是操作其中的某个字段。博主在做单点登录的时候，就是用这种数据结构存储用户信息，
     * 以cookieId作为key，设置30分钟为缓存过期时间，能很好的模拟出类似session的效果。
     */
    public void operatMap(){
        List<ProcessPo> top200 = testDataInfoMapper.findTop200();
        top200.forEach(e->{
            //新增hashMap值。
            redisTemplate.opsForHash().put("map_top200",e.getId(),e);

        });
        Object map_top200 = redisTemplate.opsForHash().get("map_top200", top200.get(0).getId());
        log.error("获取指定H,指定hash key的值 {} ",map_top200);
        List map_top2001 = redisTemplate.opsForHash().values("map_top200");
        log.error("获取指定变量中的hashMap。 {} ",map_top2001);
        Map map_top2002 = redisTemplate.opsForHash().entries("map_top200");
        log.error("获取变量中的键值对。 {} ",map_top2002);
        Boolean map_top2003 = redisTemplate.opsForHash().hasKey("map_top200", top200.get(0).getId());
        log.error("判断变量中是否有指定的map键。 {} ",map_top2003);
        Set map_top2004 = redisTemplate.opsForHash().keys("map_top200");
        log.error("通过keys(H key)方法获取变量中的键: {} ",map_top2004);
        Long map_top2005 = redisTemplate.opsForHash().size("map_top200");
        log.error("获取长度: {} ",map_top2004);
        List<String> arr=new ArrayList<>();
        arr.add(top200.get(0).getId());
        arr.add(top200.get(1).getId());
        List list = redisTemplate.opsForHash().multiGet("map_top200", arr);
        log.error("以集合方式获取: {} ",list);
        Long map_top2006 = redisTemplate.opsForHash().delete("map_top200", top200.get(0).getId(), top200.get(1).getId());
        log.error("删除键: {} ",map_top2006);
        Map newMap = new HashMap();
        newMap.put(top200.get(0).getId(),top200.get(0));
        newMap.put(top200.get(1).getId(),top200.get(1));
        redisTemplate.opsForHash().putAll("map_top200",newMap);
        log.error("批量插入:  ");
    }

    /**
     * 操作set 类型
     *因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重？因为我们的系统一般都是集群部署，使用JVM自带的Set，比较麻烦，难道为了一个做一个全局去重，再起一个公共服务，太麻烦了。
     * 另外，就是利用交集、并集、差集等操作，可以计算共同喜好，全部的喜好，自己独有的喜好等功能。
     */
    public void operatSet(){
        List<ProcessPo> top200 = testDataInfoMapper.findTop200();
        top200.forEach(e->{
            //集合中添加元素，返回添加个数。
            Long set_top200 = redisTemplate.opsForSet().add("set_top200", e);
            log.error("集合中添加元素，返回添加个数。:{}  ",set_top200);
        });
        //从集合中删除指定元素
        Long set_top2001 = redisTemplate.opsForSet().remove("set_top200", top200.get(0));
        log.error("。从集合中删除指定元素:{}  ",set_top2001);
        //从集合中随机删除一个元素，并返回该元素
        Object set_top200 = redisTemplate.opsForSet().pop("set_top200");
        log.error("。从集合中随机删除一个元素，并返回该元素:{}  ",set_top200);
        //将集合1中的指定元素移到集合2
        Boolean move = redisTemplate.opsForSet().move("set_top200", top200.get(1), "set2_top200");
        log.error("。将集合1中的指定元素移到集合2:{}  ",move);
        //获得集合大小
        Long size = redisTemplate.opsForSet().size("set_top200");
        log.error("。获得集合大小:{}  ",size);
        //判断集合中是否存在某元素
        Boolean ifExist = redisTemplate.opsForSet().isMember("set_top200", top200.get(1));
        log.error("。判断集合中是否存在某元素:{}  ",ifExist);
        Set<Object> intersection = redisTemplate.opsForSet().intersect("set_top200", "set2_top200");
        //也可以和多个key对应的集合求交集                  Set<V> intersect(K key, K otherKey);
        log.error("。求交集     :{}  ",intersection);
        //求交集并放入集合c
        Long aLong = redisTemplate.opsForSet().intersectAndStore("set_top200", "set2_top200", "set3_top200");
        log.error("。求交集并放入集合c     :{}  ",aLong);
        //也可以和多个key对应的集合求交集                 Long intersectAndStore(K key, Collection<K> otherKeys, K destKey);
        //随机获取集合中的一个元素
        System.out.println(redisTemplate.opsForSet().randomMember("games"));

        //随机获取集合中指定个数的元素（有可能重复）
        System.out.println(redisTemplate.opsForSet().randomMembers("games", 10));

        //随机获取集合中指定个数的元素（不重复）
        System.out.println(redisTemplate.opsForSet().distinctRandomMembers("games", 10));

        //添加集合d
        redisTemplate.opsForSet().add("set-d", "a", "b", "c", "d");
        //添加集合e
        redisTemplate.opsForSet().add("set-e", "c", "d", "e", "f");
        //添加集合f
        redisTemplate.opsForSet().add("set-f", "e", "f", "g", "h");

        //求差集(属于d不属于e和f)
        Set<String> difference = redisTemplate.opsForSet().difference("set-d", Arrays.asList("set-e", "set-f"));
        //添加集合m
        redisTemplate.opsForSet().add("set-m", "a", "b", "c", "d");
        //添加集合n
        redisTemplate.opsForSet().add("set-n", "c", "d", "e", "f");
        //求并集
        Set<String> union = redisTemplate.opsForSet().union("set-m", "set-n");
        System.out.println(union);
    }


}
